package soccerscope.training;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.FileWriter;
import java.util.LinkedList;

import robocup.component.actions.Action;
import robocup.component.actions.CoachAction;
import robocup.component.actions.SayAction;
import robocup.component.geometry.Vektor;
import robocup.component.speechacts.MessageFactory;
import robocup.component.worldobjects.Ball;
import robocup.component.worldobjects.Player;
import soccerscope.dialog.TrainingStatsDialog;
import soccerscope.model.ClientScene;
import soccerscope.model.PlayMode;
import soccerscope.model.WorldModel;
import soccerscope.model.PlayMode.PLAY_MODE;
import soccerscope.training.conditions.BallPossessionCondition;
import soccerscope.training.conditions.CycleCondition;
import soccerscope.training.conditions.GameCondition;
import soccerscope.training.conditions.PlayModeCondition;
import soccerscope.util.Time;

import com.thoughtworks.xstream.XStream;

public class TrainingModel {

  public static int               run                 = 1;

  public static MessageFactory    messageFactory      = new MessageFactory();

  // Preconditions:
  public ObjectTrainingModel[]    objectTrainingModel = new ObjectTrainingModel[23];

  public PLAY_MODE                startPlayMode       = PLAY_MODE.PM_PlayOn;

  // GameConditions:
  public GameCondition[]          gameCondition       = new GameCondition[3];

  // training-state flags
  private int                     prepared            = 0;

  private boolean                 started             = false;

  // private boolean finished = false;

  private boolean                 statsInit           = false;

  private LinkedList<CoachAction> list                = null;

  private LinkedList<String>      info                = null;

  private TrainingStatistics      stats               = null;

  private TrainingStatsDialog     tStats              = null;

  public void setTrainingStatsDialog(TrainingStatsDialog tsd) {

    this.tStats = tsd;
  }

  public TrainingModel(
      ClientScene scene) {

    super();
    for (int i = 0; i < 22; i++) {
      this.objectTrainingModel[i] = new PlayerTrainingModel(i);
    }
    this.objectTrainingModel[22] = new ObjectTrainingModel(22);
    for (int i = 0; i < this.objectTrainingModel.length; i++) {
      if (i < 22) {
        this.objectTrainingModel[i].setPosition(scene.player[i].pos);
        PlayerTrainingModel ptm = (PlayerTrainingModel) this.objectTrainingModel[i];
        float x = scene.player[i].vel.x;
        float y = scene.player[i].vel.y;
        Vektor v = new Vektor(Vektor.XY, y, x);
        float speed_angle = (float) v.getAngle();
        int speed = (int) ((v.getLength() / 1.2f) * 100);
        ptm.setSpeed_angle(speed_angle);
        ptm.setMax_speed(speed);
        ptm.setMin_speed(speed);
        ptm.setBody_angle(scene.player[i].angle);
      }
      else {
        this.objectTrainingModel[i].setPosition(scene.ball.pos);
        float x = scene.ball.vel.x;
        float y = scene.ball.vel.y;
        Vektor v = new Vektor(Vektor.XY, y, x);
        float speed_angle = (float) v.getAngle();
        int speed = (int) ((v.getLength() / 2.7f) * 100);
        this.objectTrainingModel[i].setSpeed_angle(speed_angle);
        this.objectTrainingModel[i].setMax_speed(speed);
        this.objectTrainingModel[i].setMin_speed(speed);
      }
    }

    this.startPlayMode = scene.getPlayModeEnum();

    // insert training game conditions:
    this.gameCondition[0] = new PlayModeCondition();
    this.gameCondition[1] = new CycleCondition();
    this.gameCondition[2] = new BallPossessionCondition();

  }

  /**
   * This method saves the given training-model
   * 
   * @return a success-value
   */
  public static boolean saveTrainingModel(TrainingModel tm,
      String filename) {

    try {
      System.out.println("saveTrainingModel");
      FileWriter fw = new FileWriter(filename);
      XStream xstream = new XStream();
      String xml = xstream.toXML(tm);
      fw.write(xml);
      fw.flush();
      fw.close();
      return true;
    }
    catch (Exception e) {
      e.printStackTrace();
      return false;
    }
  }

  /**
   * This method loads a trainingmodel from the an xml-file
   * 
   * @return - the serialized training-model
   */
  public static TrainingModel loadTrainingModel(String filename) {

    try {
      BufferedReader buff = new BufferedReader(new FileReader(filename));
      StringBuffer str = new StringBuffer();
      String tmp = null;
      while ((tmp = buff.readLine()) != null) {
        str.append(tmp);
      }

      XStream xstream = new XStream();
      TrainingModel tm = (TrainingModel) xstream.fromXML(str.toString());

      if (!tm.statsInit) {
        tm.initStats();
      }
      return tm;

    }
    catch (Exception e) {
      e.printStackTrace();
      return null;
    }
  }

  /**
   * This method checks, if the training-scenario is prepared, meaning, that all
   * objects are at their destinated positions and have the specified properties
   * 
   * @return true, if preparation is done.
   */
  public boolean isPrepared() {

    return (this.prepared == 3);
  }

  /**
   * This method prepares the training-scenario, by executing the following
   * actions: 1. Place all objects, which are part of the scenario to their
   * destinated position 2. Place all objects, which are not part of the
   * scenario very far outside the field, for example, all players who are not
   * active 3. For all players with round-view (and activity), let them turn, so
   * that they can see everything in their near environment 4. recovery, if
   * necessary
   * 
   * @return - a linked-list with trainer-commands
   */
  public void prepareTrainingScenario() {

    if (this.list == null) {
      this.list = new LinkedList<CoachAction>();
    }
    if (this.info == null) {
      this.info = new LinkedList<String>();
    }
    switch (this.prepared) {
      case 0:
        // this sets all positions of objects, which are
        // active, all others (only players) are killed on
        // server-side with command (kill_player [r|l] <num>)
        ClientScene cs = WorldModel.getSceneSet().lastScene();
        for (int i = 0; i < this.objectTrainingModel.length; i++) {
          if (i < 11) {
            CoachAction x = this.objectTrainingModel[i].getTrainerAction(i,
                cs.left);
            if (x != null) {
              this.list.add(x);
            }
          }
          else if (i < 22) {
            CoachAction x = this.objectTrainingModel[i].getTrainerAction(i,
                cs.right);
            if (x != null) {
              this.list.add(x);
            }
          }
          else {
            CoachAction x = this.objectTrainingModel[i].getTrainerAction(i,
                null);
            if (x != null) {
              this.list.add(x);
            }
          }
        }

        LinkedList<String> dead = new LinkedList<String>();
        for (int i = 0; i < this.list.size(); i++) {
          CoachAction ca = this.list.get(i);

          WorldModel.getInstance().trainer.sendActions(ca.toString());
          if (ca.getType() == Action.TYPE.COACH_KILL_PLAYER) {
            dead.add("" + (i + 1));
          }
          try {
            Thread.sleep(100);
          }
          catch (Exception e) {

          }
        }

        // broadcast killed players:
        for (int i = 0; i < dead.size(); i++) {
          String str = TrainingModel.messageFactory.encodeKillPlayer(Integer
              .parseInt(dead.get(i)));
          SayAction say = new SayAction(str, false);
          WorldModel.getInstance().trainer.sendActions(say.toString());
        }
        this.prepared++;
        break;
      case 1:
        String playmode = PlayMode
            .getPlayModeString(PlayMode.allPlayModes[PlayMode.PM_BeforeKickOff]);
        CoachAction cax = new CoachAction(playmode);
        WorldModel.getInstance().trainer.sendActions(cax.toString());
        // after placing all players, the positions of all players
        // will ce communicated by the trainer. This will replace the round
        // view
        // function. However, it will also run much faster!
        Player player = Player.getDummyInstance();
        for (int i = 0; i < this.list.size(); i++) {
          if (this.objectTrainingModel[i] instanceof PlayerTrainingModel) {
            PlayerTrainingModel ptm = (PlayerTrainingModel) this.objectTrainingModel[i];
            if (ptm.isRoundView() && ptm.isActive()) {
              CoachAction ca = this.list.get(i);
              Vektor pos = ca.getPosition();
              Vektor spd = ca.getSpeed();
              int unum = ca.getNumber();
              boolean isFriend = (ca.getTeam().indexOf("DAI") >= 0);
              // if (unum > 11) {
              // unum -= 11;
              // isFriend = false;
              // }
              player.setBodyCycle(0);
              player.setCycle(0);
              player.setFriend(isFriend);
              player.setNumber(unum);
              player.setPosition(pos);
              player.setSpeed(spd);

              this.info.add(TrainingModel.messageFactory
                  .encodeTrainerPlayerPosition(player));
              this.info.add(TrainingModel.messageFactory
                  .encodeTrainerPlayerSpeed(player));
            }
          }
          else { // Ball
            CoachAction ca = this.list.get(i);
            Vektor pos = ca.getPosition();
            Vektor spd = ca.getSpeed();
            Ball ball = new Ball(0, 0, pos, spd);
            this.info.add(TrainingModel.messageFactory
                .encodeTrainerBallPosition(ball));
            this.info.add(TrainingModel.messageFactory
                .encodeTrainerBallSpeed(ball));
          }
        }
        this.prepared++;
        break;
      case 2:
        // here, the list of actions from round-view creation are
        // removed and executed, one in each call of this method!
        if (this.info.isEmpty()) {
          this.prepared++;
        }
        else {
          String mess = this.info.removeFirst();
          SayAction say = new SayAction(mess, false);
          WorldModel.getInstance().trainer.sendActions(say.toString());
        }
        break;
    }
  }

  /**
   * This method simply changes the Play-Mode to the desired one. In most cases,
   * this will be PLAY_ON, but for Standards, this may also be: KICK_OFF,
   * KICK_IN, OFFSIDE, etc.
   */
  public void startTrainingScenario() {

    if (WorldModel.sceneSet.lastScene().time > 6000 && WorldModel.sceneSet
        .lastScene().time != Time.MAX_TIME) {
      return;
    }
    String playmode = PlayMode.getPlayModeString(this.startPlayMode);
    CoachAction ca = new CoachAction(playmode);
    WorldModel.getInstance().trainer.sendActions(ca.toString());
    this.started = true;
    System.out
        .println("Szenario Run Nr.: " + TrainingModel.run + " PlayMode: " + ca
            .toString());
    TrainingModel.run++;
  }

  /**
   * This method checks, if the scenario is already started!
   * 
   * @return true, if the scenario is running:
   */
  public boolean isStarted() {

    return this.started;
  }

  /**
   * This method resets the scenario, so that the trainer will set it up for a
   * new iteration. To do this, prepared and started are set to false (prepared =
   * 0).
   */
  public void resetScenario() {

    this.prepared = 0;
    this.started = false;
    this.list = new LinkedList<CoachAction>();
    this.info = new LinkedList<String>();

    // reset PlayMode to 'BEFORE_KICK_OFF'
    String playmode = PlayMode
        .getPlayModeString(PlayMode.allPlayModes[PlayMode.PM_BeforeKickOff]);
    CoachAction ca = new CoachAction(playmode);
    WorldModel.getInstance().trainer.sendActions(ca.toString());

    // set recover stamina/effort to agents
    ca = new CoachAction(Action.TYPE.COACH_RECOVER);
    WorldModel.getInstance().trainer.sendActions(ca.toString());

    for (int i = 0; i < this.gameCondition.length; i++) {
      this.gameCondition[i].resetCondition();
    }
  }

  /**
   * This method checks, if some of the abort-conditions are satisfield. If so,
   * the trainings-sceanrio have to be restarted, and therefore a true has to be
   * returned!
   * 
   * @return true, if the iteration is over.
   */
  public boolean checkScenarioConditions(ClientScene scene) {

    for (int i = 0; i < this.gameCondition.length; i++) {
      if (this.gameCondition[i].isActive()) {
        if (this.gameCondition[i].isTrue(scene)) {
          // increase counter for training runs
          this.stats.incElement(0);
          // increase counter for condition
          if (i == 1) {
            this.stats.incElement(1);
          }
          if (i == 2) {
            this.stats.incElement(2);
          }
          if (i == 0) {
            this.stats.incElement(((PlayModeCondition) (this.gameCondition[0]))
                .getFulfillingIndex());
          }
          if (this.tStats != null) {
            this.tStats.update();
          }
          return true;
        }
      }
    }
    return false;
  }

  private void initStats() {

    this.stats = new TrainingStatistics();
    if (this.gameCondition[1].isActive()) {
      this.stats.addElement(((CycleCondition) this.gameCondition[1])
          .getStatIndex());
    }
    if (this.gameCondition[2].isActive()) {
      this.stats.addElement(((BallPossessionCondition) this.gameCondition[2])
          .getStatIndex());
    }
    if (this.gameCondition[0].isActive()) {
      int[] pmodes = ((PlayModeCondition) this.gameCondition[0])
          .getStatIndizes();
      for (int i = 0; i < pmodes.length; i++) {
        this.stats.addElement(pmodes[i]);
      }
    }
    this.statsInit = true;
  }

  public TrainingStatsElement[] getStats() {

    return this.stats.getElementsArray();
  }
}
